/*************************************************************************
 * Copyright 2008 David Almilli
 *  
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 *  
 *     http://www.apache.org/licenses/LICENSE-2.0 
 *      
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License.
 **************************************************************************/
package com.tivo.hme.bananas;

import static com.tivo.hme.bananas.IBananasPlus.*;

import com.tivo.hme.bananas.BSkin.Element;
import com.tivo.hme.bananas.BSplitHighlights.ISplitHighlightsLayout;
import com.tivo.hme.sdk.HmeEvent;
import com.tivo.hme.sdk.Resource;
import java.awt.Rectangle;

public abstract class BMovableListPlus<E> extends BListPlus<E> {
    private static final int EVT_MOVE_VALUE = HmeEvent.EVT_RESERVED + 21;
    
	public static final String ACTION_SELECT_RIGHT = "select-right";
	public static final String ACTION_SELECT_LEFT = "select-left";
	public static final String ACTION_MOVE_UP = "move-up";
	public static final String ACTION_MOVE_DOWN = "move-down";
	public static final String ACTION_MOVE_PAGE_UP = "move-page-up";
	public static final String ACTION_MOVE_PAGE_DOWN = "move-page-down";
	
	protected boolean moverSelected;
	
	protected BHighlightsPlus activeHighlights;

	public BMovableListPlus(BView parent, int x, int y, int width, int height) {
		this(parent, x, y, width, height, true);
	}
	
	public BMovableListPlus(BView parent, int x, int y, int width, int height, boolean visible) {
		this(parent, x, y, width, height, ViewUtils.getHeight(parent, H_BAR), visible);
	}

	public BMovableListPlus(BView parent, int x, int y, int width, int height, int rowHeight) {
		this(parent, x, y, width, height, rowHeight, true);
	}

	public BMovableListPlus(BView parent, int x, int y, int width, int height, int rowHeight,
			boolean visible) {
		this(parent, ViewUtils.getBounds(parent, x, y, width, height), rowHeight, visible);
	}

    public BMovableListPlus(BView parent, com.tivo.hme.bananas.layout.Layout layout) {
        this(parent, layout.getBounds(), ViewUtils.getHeight(parent, H_BAR), true);
    }

    public BMovableListPlus(BView parent, com.tivo.hme.bananas.layout.Layout layout, int rowHeight, boolean visible) {
        this(parent, layout.getBounds(), rowHeight, visible);
    }

	protected BMovableListPlus(BView parent, Rectangle bounds, int rowHeight, boolean visible) {
		super(parent, bounds, rowHeight, visible);
	}

	@Override
	protected BHighlightsPlus createRowHighlights() {
		BSkin skin = getBApp().getSkin();
		Element up = skin.get(H_UP);
		Element down = skin.get(H_DOWN);
        Element left = skin.get(H_LEFT);
        Element right = skin.get(H_RIGHT);
		Element bar = skin.get(H_BAR);
		int cap = bar.getInt(P_CAP, 30);
        
        int spacing = Math.max(left.getWidth(), right.getWidth());
		
		BSplitHighlights h = new BSplitHighlights(new ListLayout(), 
				2*cap + up.getWidth() + down.getWidth(), spacing);
		this.activeHighlights = h.getLeftHighlights();
		return h;
	}
    
    public int getRowWidth() {
        BSplitHighlights h = (BSplitHighlights)getRowHighlights();
        return h.getLeftHighlights().getRect().width;
    }
    
    public int getMoverWidth() {
        BSplitHighlights h = (BSplitHighlights)getRowHighlights();
        return h.getRightHighlights().getRect().width;
    }

	@Override
    protected BView createRowContainer(int index, int x, int y, int width, int height) {
        return new MoverRowView(this, x, y, width, height);
    }

    @Override
    protected void handleCreateRow(BView parent, int index) {
        MoverRowView row = (MoverRowView)parent;
        createRow(row.getLeft(), index);
    }

    @Override
    public void setBarAndArrows(int bar_left, int bar_right,
                                Object action_left, Object action_right) {
		synchronized (lock) {
	    	BSplitHighlights h = (BSplitHighlights)getRowHighlights();
	    	h.getLeftHighlights().setBarAndArrows(bar_left, BAR_DEFAULT, FLAG_DISABLED_NOFOCUS, FLAG_VIS_TRUE, action_left, ACTION_SELECT_RIGHT, false);
	    	h.getRightHighlights().setBarAndArrows(BAR_DEFAULT, bar_right, FLAG_DISABLED_NOFOCUS, FLAG_VIS_TRUE, ACTION_SELECT_LEFT, action_right, false);
	
            setUpDownArrows(h, bar_left, bar_right);
	    	
			BSkin skin = getBApp().getSkin();
			Element up = skin.get(H_UP);
			Element down = skin.get(H_DOWN);
			
			createMoverArrow(h.getRightHighlights(), H_UP, A_CENTER - (up.getWidth())/2, A_CENTER, ACTION_MOVE_UP);
			createMoverArrow(h.getRightHighlights(), H_DOWN, A_CENTER + (down.getWidth())/2, A_CENTER, ACTION_MOVE_DOWN);
		}
    }
	
	protected void createMoverArrow(BHighlightsPlus hs, String name, int anchorX, int anchorY, Object action) {
		synchronized (lock) {
            if (action == null) {
                throw new RuntimeException("arrow action cannot be null");
            }

            BPoint p = getBApp().getSkin().getAnchor(name, hs.getRect(), anchorX, anchorY);
			BHighlight h = new BVisibleHighlight(name, action, p.x, p.y);
            hs.set(h);
        }
	}

	@Override
	public boolean handleAction(BView view, Object action) {
        
		if (action.equals(ACTION_SELECT_RIGHT)) {
			setMoverSelected(true);
			getBApp().play("updown.snd");
			return true;
			
		} else if (action.equals(ACTION_SELECT_LEFT)) {
			setMoverSelected(false);
			getBApp().play("updown.snd");
			return true;
			
		} else if (action.equals(ACTION_MOVE_UP)) {
			move(true, false);
			return true;
			
		} else if (action.equals(ACTION_MOVE_DOWN)) {
			move(false, false);
			return true;
			
		} else if (action.equals(ACTION_MOVE_PAGE_UP)) {
			move(true, true);
			return true;
			
		} else if (action.equals(ACTION_MOVE_PAGE_DOWN)) {
			move(false, true);
			return true;
		}
		return super.handleAction(view, action);
	}
	
	protected void move(boolean up, boolean page) {
        int pagesize = getNVisibleRows() - 1;
        int newfocus = -1;
        int top = getTop();
        int newtop = top;
        int focused = getFocus();
        int size = size();
        int code = 0;

        if (up && !page) {
        	//up one
		    code = KEY_UP;
		    newfocus = focused - 1;    
		    if (newfocus>=0 && newfocus<size){
		        Object rowItem = get(newfocus);
		        if (rowItem instanceof BListItemExtensions &&
		                !((BListItemExtensions)rowItem).isSelectable()){
		            newfocus = getPreviousSelectable(newfocus);
		        }
		    }
        } else if (!up && !page) {
        	//down one
		    code = KEY_DOWN;
    	    newfocus = focused + 1;    
    	    if (newfocus>=0 && newfocus<size){
    	        Object rowItem = get(newfocus);
    	        if (rowItem instanceof BListItemExtensions &&
    	                !((BListItemExtensions)rowItem).isSelectable()){
    	            newfocus = getNextSelectable(newfocus);
    	        }
    	    }
        } else if (up && page) {
        	//page up
		    code = KEY_CHANNELUP;
    	    newfocus = top; 
    	    if (newfocus>=0 && newfocus<size){
    	        Object rowItem = get(newfocus);
    	        if (rowItem instanceof BListItemExtensions &&
    	                !((BListItemExtensions)rowItem).isSelectable()){
    	            newfocus = getNextSelectable(newfocus);
    	        }
    	    }
    	    newtop = newfocus - pagesize;
        } else if (!up && page) {
        	//page down
		    code = KEY_CHANNELDOWN;
    	    newfocus = top + pagesize; 
            if (newfocus>=size){
                newfocus = size-1;
            }
    	    if (newfocus>=0 && newfocus<size){
    	        Object rowItem = get(newfocus);
    	        if (rowItem instanceof BListItemExtensions &&
    	                !((BListItemExtensions)rowItem).isSelectable()){
    	            newfocus = getPreviousSelectable(newfocus);
    	        }
    	    }
    	    newtop = newfocus;
        }

        if (focused != -1) {
            int max = size() - 1;

            // normalize the newfocus/newtop values
            newfocus = Math.max(Math.min(newfocus, max), 0);
            if (newtop == top) {
                // check if we moved off the current page
                if (newfocus < top) {
                    newtop = newfocus - (pagesize - 1);
                } else if (newfocus > top + pagesize) {
                    newtop = newfocus - 1;
                }
            }
            newtop = Math.max(Math.min(newtop, max - pagesize), 0);

            if (newfocus == focused && newtop == top) {
                // no changes - bonk!
                return;
            }

            // Play the sound manually because sometimes we change top without
            // changing the focus and by default the key is considered not
            // handled if the focus doesn't change (reasonable behavior most of
            // the time).
            getBApp().playSoundForKey(code, true, true);
            
            setIgnoreRefreshes(true);
            E value = get(focused);
            try {
	            //move the value
                moveValue(focused, newfocus, value);
	            
	            if (newtop != top) {
	                setAnimate(true);
	                setTop(newtop, newfocus);
	            }
	
	            setFocus(newfocus, true);
	            
            } finally {
            	setIgnoreRefreshes(false);
	            
	            //dispatch the move event
	            postEvent(new MoveEvent(EVT_MOVE_VALUE, getID(), focused, newfocus, value));
	            
            	refresh();
            }
        }
	}
	
	/**
	 * Performs the actual move from one location in the list to another.
	 * @param oldIndex the index location where the value currently exists
	 * @param newIndex the index location where the value is moving to
	 * @param value the value that is being moved
	 */
	protected void moveValue(int oldIndex, int newIndex, E value) {
        remove(oldIndex);
        add(newIndex, value);
	}

	@Override
    public void refresh() {
		if (isIgnoreRefreshes()) {
			return;
		}
		synchronized (lock) {
			super.refresh();
			
			int focused = getFocus();
			if (focused != -1) {
				//update the mover arrows
		        Resource anim = animate ? getResource(ANIM) : null;
		    	BSplitHighlights h = (BSplitHighlights)getRowHighlights();
                BHighlight up = h.getRightHighlights().get(H_UP);
                BHighlight down = h.getRightHighlights().get(H_DOWN);
                if (up != null && down != null) {
                    up.setVisible((focused > 0) ? H_VIS_FOCUS : H_VIS_FALSE);
                    down.setVisible((focused < size() - 1) ? H_VIS_FOCUS : H_VIS_FALSE);
                }
                rowHighlights.refresh(anim);
			}
		}
    }
	
	public void setMoverSelected(boolean moverSelected) {
		synchronized (lock) {
			if (this.moverSelected != moverSelected) {
		    	BSplitHighlights h = (BSplitHighlights)getRowHighlights();
				this.activeHighlights = (moverSelected) ? h.getRightHighlights() : h.getLeftHighlights();
				this.moverSelected = moverSelected;
				h.refresh();
			}
		}
	}
	
	public boolean isMoverSelected() {
		return moverSelected;
	}
	
	@Override
	public boolean handleKeyPress(int code, long rawcode) {
	    synchronized (lock) {
	        if (activeHighlights != null) {
	        	if (code >= KEY_UP && code <= KEY_RIGHT) {
		            BHighlight h = activeHighlights.get(ARROW_NAMES[code - KEY_UP]);
		            if (h != null && h.getAction() != null) {
		                postEvent(new BEvent.Action(this, h.getAction()));
		                return true;
		            }
	        	} else if (code == KEY_CHANNELUP) {
	        		BHighlight h = activeHighlights.get(H_UP);
		            if (h != null && ACTION_MOVE_UP.equals(h.getAction())) {
		                postEvent(new BEvent.Action(this, ACTION_MOVE_PAGE_UP));
		                return true;
		            }
	        	} else if (code == KEY_CHANNELDOWN) {
	        		BHighlight h = activeHighlights.get(H_DOWN);
		            if (h != null && ACTION_MOVE_DOWN.equals(h.getAction())) {
		                postEvent(new BEvent.Action(this, ACTION_MOVE_PAGE_DOWN));
		                return true;
		            }
	        	}
	        }
			return super.handleKeyPress(code, rawcode);
	    }
	}

	public class ListLayout extends BListPlus.ListLayout implements ISplitHighlightsLayout {

		public boolean getLeftHighlightIsVisible(int visible) {
			return !moverSelected;
		}

		public boolean getRightHighlightIsVisible(int visible) {
			return moverSelected;
		}
	}
	
	public static class BVisibleHighlight extends BHighlightPlus {

		public BVisibleHighlight(String name, Object action, int dx, int dy) {
			super(name, action, dx, dy);
		}

		@Override
		public boolean getHighlightIsVisible(BHighlights h, BScreen s, BRect r, Resource animation) {
			return getVisible() != H_VIS_FALSE;
		}
	}
    
    public class MoverRowView extends RowView {
        private BView left;

        public MoverRowView(BView parent, int x, int y, int width, int height) {
            super(parent, x, y, width, height);
            left = new BView(this, 0, 0, getRowWidth(), height);
        }

        public BView getLeft() {
            return left;
        }
    }
	
	public static class MoveEvent extends HmeEvent {
		private int oldIndex;
		private int newIndex;
		private Object value;

		public MoveEvent(int opcode, int id, int oldIndex, int newIndex, Object value) {
			super(opcode, id);
			this.oldIndex = oldIndex;
			this.newIndex = newIndex;
			this.value = value;
		}

		public int getOldIndex() {
			return oldIndex;
		}

		public int getNewIndex() {
			return newIndex;
		}

		public Object getValue() {
			return value;
		}
	}
}
